package com.woniuxy.qimengcommons.util;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.woniuxy.qimengcommons.exception.TokenIllegalException;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class JwtUtil {

    @Resource
    private ValueOperations valueOperations;
    @Resource
    private RedisTemplate redisTemplate;

    /**
     * 过期时间
     */
    private int time01 = 60;
    /**
     * 刷新时间
     *
     * 60 - 30 = 30 分钟时间
     * 只要在30分钟之内，发送请求到后端，后端都可以给前端更换令牌
     *
     */
    private int time02 = 30;
    /**
     * 私钥（盐值）
     */
    private String secret = "tomAndJerry";

    /**
     * 认证Token
     * @param token
     * @return
     */
    public PayLoad validateToken(String token){
        //判断Redis中是否存在该token令牌，存在才可以校验，如果不存在表示前端传递的Token是非法令牌
        if(!redisTemplate.hasKey(token)){
            throw new TokenIllegalException();
        }

        //创建一个JWT的验证工具实例
        JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secret)).build();

        try {
            //针对Token进行解密
            DecodedJWT decodedJWT = verifier.verify(token);
            //获得一些载荷的信息(目的：刷新令牌时使用)
            PayLoad payLoad = getPublicClaim(decodedJWT);
            //获得私有的载荷信息
            payLoad = getPrivateClaim(payLoad,decodedJWT);

            return payLoad;

            //可能抛出加密算法不匹配异常
        }catch (AlgorithmMismatchException e){
            throw e;
            //可能抛出令牌失效异常
        }catch (TokenExpiredException e){
            throw e;
        }catch (Exception e){
            throw e;
        }
    }

    /**
     * 刷新令牌的方法
     * @param payLoad
     * @param token
     */
    public String reflushToken(PayLoad payLoad,String token) {
        Map<String,Object> claims = payLoad.getClaims();

        //获得刷新时间，并转换为Date格式
        Claim claim = (Claim)claims.get("refreshAt");
        Date refreshAt = claim.asDate();
        //获得失效时间
        Date expiresAt = payLoad.getExpiresAt();
        Date currentDate = new Date();
        if(currentDate.before(expiresAt) && currentDate.after(refreshAt)){
            //从Redis中返回自定义的载荷数据
            Map<String,Object> oldClaims = (Map<String,Object>)valueOperations.get(token);
            //将老令牌从Redis中清理掉
            redisTemplate.delete(token);
            String newToken = createToken(oldClaims);
            //将新令牌设置到Redis中去
            valueOperations.set(newToken,oldClaims);
            //产生一个新的令牌
            return newToken;
        }
        return null;
    }

    /**
     * 从JWT解密的内容中，取得私有的载荷信息
     * @param payLoad 载荷对象
     * @param decodedJWT
     * @return
     */
    private PayLoad getPrivateClaim(PayLoad payLoad, DecodedJWT decodedJWT) {
        Map<String,Object> claims = new HashMap<>();
        //获得私有的载荷
        decodedJWT.getClaims().forEach((k,v)->{
            System.out.println(v);
            claims.put(k,v);
        });
        payLoad.setClaims(claims);
        return payLoad;
    }

    /**
     * 从JWT解密的内容中，取得公共的载荷信息
     * @param decodedJWT
     * @return
     */
    private PayLoad getPublicClaim(DecodedJWT decodedJWT) {
        PayLoad payLoad = new PayLoad();
        //获得 发布者的信息
        payLoad.setIssuer(decodedJWT.getIssuer());
        //获得 令牌的主题
        payLoad.setSubject(decodedJWT.getSubject());
        //获得 令牌的接收者
        decodedJWT.getAudience().forEach((s)->{
            payLoad.setAudience(s);
        });
        //获得 发布的时间
        payLoad.setIssuedAt(decodedJWT.getIssuedAt());
        payLoad.setExpiresAt(decodedJWT.getExpiresAt());
        return payLoad;
    }


    /**
     * claims 自定义签名（需要附加到载荷的信息）
     * @return 返回的Token令牌
     */
    public String createToken(Map<String,Object> claims){
        //设置载荷
        PayLoad payLoad = createPayload(time01,time02);
        payLoad.setClaims(claims);
        //设置签证的加密处理
        Algorithm hmac256 = Algorithm.HMAC256(secret);
        return createToken(payLoad,hmac256);
    }

    /**
     * 根据载荷以及签证加密算法，进行产生令牌
     * @param payLoad
     * @param hmac256
     * @return
     */
    private String createToken(PayLoad payLoad, Algorithm hmac256) {
        //构建头
        JWTCreator.Builder builder = createHeader(hmac256);

        //构建载荷中的公共的声明
        JWTCreator.Builder publicClaimBuider = createPublicClaim(builder,payLoad);

        //构建载荷中的私有的声明
        JWTCreator.Builder privateClaimBuider = createPrivateClaim(publicClaimBuider,payLoad);
        String token = privateClaimBuider.sign(hmac256);
        return token;
    }

    /**
     * 构建载荷中的私有的声明
     * @param builder
     * @param payLoad
     * @return
     */
    private JWTCreator.Builder createPrivateClaim(JWTCreator.Builder builder, PayLoad payLoad) {
        //获取 自定义的签名
        Map<String,Object> claims = payLoad.getClaims();
        if(!CollectionUtils.isEmpty(claims)){
            // lambda 表达式的应用
            claims.forEach((k,v)->{
                builder.withClaim(k,(String) v);
            });
        }

        //设置令牌的刷新时间
        builder.withClaim("refreshAt",payLoad.getRefreshAt());
        return builder;
    }

    /**
     * 构建载荷中的公共的声明
     * @param builder
     * @param payLoad
     * @return
     */
    private JWTCreator.Builder createPublicClaim(JWTCreator.Builder builder, PayLoad payLoad) {
        // 构造签名者
        builder.withIssuer(payLoad.getIssuer());
        // 构造签名主题
        builder.withSubject(payLoad.getSubject());
        //设置签名时间（发令牌的时间）
        builder.withIssuedAt(payLoad.getIssuedAt());
        //设置过期时间
        builder.withExpiresAt(payLoad.getExpiresAt());

        //设置 令牌的接受者
        payLoad.getAudience().forEach((s)->{
            builder.withAudience(s);
        });
        return builder;
    }

    /**
     * 产生JWT的头(Header)
     * @param hmac256
     * @return
     */
    private JWTCreator.Builder createHeader(Algorithm hmac256) {

        Map<String,Object> header = new HashMap<>();
        header.put("alg",hmac256.getName());
        header.put("typ","JWT");
        return JWT.create().withHeader(header);
    }

    /**
     * 产生载荷
     * @param time01 过期时间
     * @param time02 刷新时间
     * @return
     */
    private PayLoad createPayload(int time01, int time02) {
        PayLoad payLoad = new PayLoad();
        //设定令牌的发布者
        payLoad.setIssuer("woniu");
        //设定Token令牌的主题
        payLoad.setSubject("microserver-study");
        //设定哪些程序可以使用该令牌
        payLoad.setAudience("web","app","mapp");
        //设定令牌产生的时间，以及过期时间，刷新时间
        Date date = new Date();
        payLoad.setIssuedAt(date);
        //设置过期时间
        payLoad.setExpiresAt(DateUtil.getAfterDate(date,time01));
        //设置刷新时间
        payLoad.setRefreshAt(DateUtil.getAfterDate(date,time02));
        return payLoad;
    }

}
